#!/usr/bin/env python
# -*- coding: UTF-8 -*-

try:
	import pdb
	import pygame
	import sys
	from lib import utils
	from lib.animateable import Animateable
	from panel import *
	from pygame.locals import *
except ImportError, err:
	print "Could not load %s module." % (err)
	sys.exit(2)

'''
Creature

Any and all sprites that move and attack should be instances or
subclasses of this class.

Needs a lot of work. It's currently hardcoded to always face South.
'''

WALKING_PARAM = (48, 6)

class Creature(pygame.sprite.DirtySprite, AnimateableDiag):
	def __init__(self, x, y, layers, field, cache):
		pygame.sprite.DirtySprite.__init__(self)
		AnimateableDiag.__init__(self)
		self.mov_seq = 0
		self.frame = 0

		self.x = x + 48
		self.y = y + 48

		self.cache = cache.get(utils.ERWIN_3)

		self.rect = pygame.rect.Rect((self.x, self.y, 48, 48))
		self.image = pygame.surface.Surface(self.rect.size).convert()
		self.image.blit(self.cache.get(utils.DIR_DOWN, 0), (0,0))
		self.image.set_colorkey(self.image.get_at((0,0)), RLEACCEL)

		self.moving = False
		self.dir = utils.DIR_DOWN

		self.walk_params = list(WALKING_PARAM)

		self.MV = 50
		self.atk_range = 5
		self.action_menu_options = ('Move', 'Attack', 'Magic',
									'Rally', 'Tactics', 'Status')
		self.set_anim_params(48)



		self.focused = False
		self.selected = False
		self.move_range = [None]

		self.layers = layers
		self.layers.add(self, layer = 1)

		self.move = []

		self.field = field


	def update(self):
		if self.animating != [[],[]]:
			self.slide()
			self.rect.topleft = (self.x, self.y)

		self.walking_anim()

	def walking_anim(self):
		"""
		Animates walking if self.move has tile positions to go in order

		I think it works fine? Not sure if blitting the image every frame is a smart thing though
		"""
		if self.move != []:

			if self.walk_params[1] == 1:
				step = self.walk_params[0]
			else:
				step = int(ceil(self.walk_params[0] / self.walk_params[1]))

			x = self.move[0][0] * 48 - (self.rect.left  + self.field.get_tile_pos()[0] * 48) + 48
			y = self.move[0][1] * 48 - (self.rect.top  + self.field.get_tile_pos()[1] * 48) + 48

			if x > 0:
				self.rect.left += step
				self.dir = utils.DIR_RIGHT
			elif x < 0:
				self.rect.left -= step
				self.dir = utils.DIR_LEFT
			elif y > 0:
				self.rect.top += step
				self.dir = utils.DIR_DOWN
			elif y < 0:
				self.rect.top -= step
				self.dir = utils.DIR_UP

			self.walk_params[0] -= step
			self.walk_params[1] -= 1
			self.image.blit(self.cache.get(self.dir, self.mov_seq), (0,0))

			if not self.walk_params[0]:
				self.walk_params = list(WALKING_PARAM)
				self.move.pop(0)
			self.dirty = 1

		if self.focused == True or self.selected == True:
			# every 20 frames, rest on the other foot
			self.frame = (self.frame + 1) % 20
			if self.frame == 0:
				self.mov_seq  = (self.mov_seq + 1) % 2
				self.image.blit(self.cache.get(self.dir, self.mov_seq), (0,0))
				self.dirty = 1

	def select(self):
		"""
		Toggles selected - only used to animate if not focused now

		Redundant if can't focus on others when selected
		"""
		if self.selected == True:
			self.selected = False
		else:
			self.selected = True
		return self

	def focus(self):
		"""
		Toggles focus(ie. hover over selection) of unit for walking animation
		"""
		if self.focused == True:
			self.focused = False
			self.image.blit(self.cache.get(utils.DIR_DOWN, 0), (0,0))
			self.mov_seq = 0
			self.dirty = 1
		else:
			self.focused = True
			self.image.blit(self.cache.get(utils.DIR_DOWN, 1), (0,0))
			self.mov_seq = 1
			self.dirty = 1
		return self


	def c_move(self, key, mod):
		'''
		move() is NOT walk()
		move() moves the sprite, walk() makes the creature walk (eventually)

		We can cheat and simplify this method, since we know this
		object never moves unless the Battlefield does.
		'''
		self.mod = mod
		if key in (K_UP, K_RIGHT, K_DOWN, K_LEFT):
			self.moving = key
			return key
		else:
			return 0

	def r_move(self, key, mod):
		moved = self.c_move(key, mod)

		if moved:
			if key == K_UP: self.animating[1].append(moved and K_DOWN)
			elif key == K_RIGHT: self.animating[0].append(moved and K_LEFT)
			elif key == K_DOWN: self.animating[1].append(moved and K_UP)
			elif key == K_LEFT: self.animating[0].append(moved and K_RIGHT)
		return self.animating

	def walk(self, dest):
		"""
		Sets walk to destination

		Creates the move path list, remembers old_pos for canceling, sets highlighting
		"""
		overlay = self.layers.get_sprites_from_layer(2)[0]
		move = self.get_move_list(dest)
		overlay.set_highlight(move)
		self.old_pos = self.rect.topleft
		self.move = move[1:]


	def get_tile_pos(self):
		"""
		Returns Tile Position of unit in overall battlefield
		"""
		x = self.rect.left / 48 + self.field.get_tile_pos()[0] - 1
		y = self.rect.top / 48 + self.field.get_tile_pos()[1] - 1
		return (x, y)

	def confirm_move(self):
		"""
		Confirms movement command

		Updates unit array in units layer, also resets this unit. Returns false
		if unit still in process of moving

		Add turn finished!
		"""
		if self.move == []:
			overlay = self.layers.get_sprites_from_layer(2)[0]
			x, y = self.old_pos
			x = x / 48 + self.field.get_tile_pos()[0] - 1
			y = y / 48 + self.field.get_tile_pos()[1] - 1

			self.x, self.y = self.rect.topleft

			new_x, new_y = self.get_tile_pos()

			overlay[new_x][new_y] = self
			overlay[x][y] = None

			self.dir = utils.DIR_DOWN
			self.image.blit(self.cache.get(utils.DIR_DOWN, 1), (0,0))
			self.walk_params = list(WALKING_PARAM)
			self.dirty = 1
			return True
		return False


	def command(self, type):
		"""
		Generic command called from creature menu

		Currently only runs Attack
		"""
		cursor = self.layers.get_sprites_from_layer(6)[0]
		overlay = self.layers.get_sprites_from_layer(2)[0]

		if type == "Attack":
			cursor.set_mode("Attack")
			overlay.set_highlight(self.get_attack_range())
			overlay.select_unit(self)
		elif type == "Move":
			cursor.set_mode("Move")
			overlay.set_highlight(self.get_move_range())
			overlay.select_unit(self)


	def get_move_range(self):
		"""
		Returns a list of possible movement tiles (tile co-ords)

		Uses an implementation of Dijkstra's Algorithm
		"""

		pos = self.get_tile_pos()

		if pos == self.move_range[0]:
			return self.move_range

		#list of open nodes [0] is pos, [1] is cost to get there
		open = [(pos, 0)]

		#list of closed nodes [0] is pos, [1] is cost to get there
		closed = [(pos, 0)]

		#list of selected nodes
		selected = []
		selected.append(pos)

		bounds = self.field.get_tile_size()
	
		while open != []:
			#takes last open node
			open.sort(lambda x, y: cmp(y[1], x[1]))
			(x, y), cost = open.pop()

			#gets all neighbours of that node
			for (nx, ny) in self.get_neighbours(x, y):

				#gets cost to travel onto that neighbour
				new_cost = self.field[nx][ny].move

				#if impassable, ignore
				if new_cost != -1 and 0 <= nx < bounds[0] and 0 <= ny < bounds[1]:
					
					#calculates new total cost to get to neighbour
					new_cost += cost

					zip1, zip2 = zip(*closed)
					#if node is already closed
					if (nx, ny) in zip1:
						i = list(zip1).index((nx,ny))

						#check if new cost is less then before and moveable
						if new_cost < closed[i][1] and new_cost <= self.MV:
							#removes closed from closed, adds to open, selected
							closed.pop(i)
							open.append((closed[i][0], new_cost))
							if (nx, ny) not in selected:
								selected.append((nx,ny))

					#if node is not closed already
					else:
						#if node moveable, add to open + selected
						if new_cost <= self.MV:
							if (nx, ny) not in selected:
								selected.append((nx, ny))
								open.append(((nx, ny), new_cost))
						#if node not moveable, add to closed
						else:
							closed.append(((nx, ny), new_cost))



		self.move_range = selected
		return selected

	def cancel(self):
		"""
		Cancels command

		Resets unit back to original position and parameters
		"""
		self.rect.topleft = self.old_pos
		self.move = []
		self.dir = utils.DIR_DOWN
		self.image.blit(self.cache.get(utils.DIR_DOWN, 1), (0,0))
		self.walk_params = list(WALKING_PARAM)
		self.dirty = 1

	def get_move_list(self, dest):
		"""
		Messy A* like algorithm to find route to destination tile
		"""
		pos = self.get_tile_pos()
		adist = abs(dest[0] - pos[0]) + abs(dest[1] - pos[1])
		open = [[adist, 0, pos]]

		while True:
			open.sort(lambda x, y: cmp(y[0], x[0]))
			path = open.pop()

			if path[0] == 0:
				return path[2:]

			x, y = path[-1]
			cost = path[1]

			for (nx, ny) in self.get_neighbours(x, y):
					if (nx, ny) in self.move_range:
						new_cost = self.field[nx][ny].move

						if new_cost != -1:
							new_cost += cost

							if new_cost <= self.MV:
								dist = abs(dest[0] - nx) + abs(dest[1] - ny)
								new_path = path[:]
								new_path[0] = dist
								new_path[1] = new_cost
								new_path.append((nx, ny))
								open.append(new_path)


	def get_neighbours(self, x, y):
		"""
		Returns the four neighbour co-ords (tile co-ords)
		"""
		return ((x+1, y),(x-1, y),(x, y+1),(x, y-1))


	def get_attack_range(self):
		"""
		Calculates potential attack range (No terrain cost involved)
		"""
		range = []
		(ax, ay) = self.get_tile_pos()
		for x in xrange(self.atk_range + 1):
			for y in xrange(self.atk_range + 1):
				if x + y <= self.atk_range:
					if (ax + x, ay + y) not in range:
						range.append((ax + x, ay + y))
					if (ax - x, ay + y) not in range:
						range.append((ax - x, ay + y))
					if (ax + x, ay - y) not in range:
						range.append((ax + x, ay - y))
					if (ax - x, ay - y) not in range:
						range.append((ax - x, ay - y))

		return range


'''
These are some properties that a creature should have

	self.HP = 10
	self.AT = 0
	self.DF = 0
	self.MV = 3
	self.magic_AT = 0
	self.magic_DF = 0
	self.atk_range = 1
	self.cmd_range = 2

	self.magics = ()
	self.summons = ()
	self.is_commander = 0

	self.has_acted = 0
	self.tactic = 0

	self.facing = SOUTH
'''
